Skip to content

Feat: AES LightEngine CCM Mode - #126

Merged
dghgit merged 45 commits into
bcgit:feature/xof-cshakefrom
officialfrancismendoza:feature/officialfrancismendoza/125-AES-lightengine-CCM-mode
Sep 28, 2026
Merged

dghgit merged 45 commits into
bcgit:feature/xof-cshakefrom
officialfrancismendoza:feature/officialfrancismendoza/125-AES-lightengine-CCM-mode

Conversation

@officialfrancismendoza

@officialfrancismendoza officialfrancismendoza commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Builds off AEAD Cipher split PR (#120) to add CCM mode for AES LightEngine (#125)

@officialfrancismendoza officialfrancismendoza added the enhancement New feature or request label Sep 10, 2026
@officialfrancismendoza
officialfrancismendoza changed the base branch from feature/symmetric-cipher to release/0.1.3alpha September 14, 2026 05:35
@officialfrancismendoza
officialfrancismendoza changed the base branch from release/0.1.3alpha to feature/symmetric-cipher September 14, 2026 05:40
@officialfrancismendoza
officialfrancismendoza force-pushed the feature/officialfrancismendoza/125-AES-lightengine-CCM-mode branch from 1d9bf20 to 63b9df6 Compare September 14, 2026 15:38

@dghgit dghgit left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like it's getting there. I've sent the report.

Make sure the update to .gitignore is removed - it's wrong! Delete the commit or revert it.

@officialfrancismendoza
officialfrancismendoza force-pushed the feature/officialfrancismendoza/125-AES-lightengine-CCM-mode branch from fcb9b75 to 63b9df6 Compare September 15, 2026 17:43
officialfrancismendoza added a commit to officialfrancismendoza/bc-rust that referenced this pull request Sep 15, 2026
read_from_file's hex-or-raw heuristic is fine for a key, where a wrong guess
only produces a mismatch, but for a CCM nonce it can turn two distinct
binary nonce files into the same nonce value if both happen to be valid hex
text for it -- and a repeated nonce under one key breaks CCM's
authentication (SP 800-38C Appendix B). Add read_from_file_raw and use it
for --nonce-file specifically; --nonce (hex on the command line) is
unaffected. PR bcgit#126 review, finding F1.
officialfrancismendoza added a commit to officialfrancismendoza/bc-rust that referenced this pull request Sep 15, 2026
…payload limit a compile error

Ccm::y held Yr (the raw tag before the S0 mask) and every intermediate
CBC-MAC chaining value in a plain array, unlike the keystream beside it,
which is a Secret for the same reason; wrap it and finish_mac's local S0
the same way. Separately, CcmEncryptor/CcmDecryptor's BUFFER_LEN could
exceed the payload limit NONCE_LEN implies (A.1's 2^8q - 1) and only fail
at do_*_final, after buffering the whole message for nothing; assert the
relationship at construction instead, which also makes MAX_PAYLOAD_LEN pub
and lets do_*_final's # Errors sections state the guarantee precisely.
Document the same capacity error as a general possibility on the trait's
do_update_aad/do_update_out. PR bcgit#126 review, findings F3 and F4.
officialfrancismendoza added a commit to officialfrancismendoza/bc-rust that referenced this pull request Sep 15, 2026
apply_keystream generated one counter block per encrypt_block call, even
though A.3's Ctrj depends only on j and the counter blocks are exactly as
independent as CTR's -- only the CBC-MAC half is genuinely serial (Sec 6.1
step 3). Restructure it like Ctr::apply: finish any open keystream block
byte-wise, batch aligned whole blocks through encrypt_4blocks/encrypt_2blocks,
then finish the tail byte-wise. Measured ~35-38% throughput gain (26->36
MiB/s for AES-128, no AAD; matches the buffering pair too), all 480 ACVP
cases and 4 Appendix C vectors still pass.

That made three doc passages actively wrong, since they said this was
inherent: modes/src/lib.rs's mode comparison, modes_benches.rs's CCM doc
comment (both rewritten with the new ratios against CTR), and lib.rs's
"CCM takes no direction"/"there is no direction parameter" claims, which
were already false against the code (Dir is very much a parameter) and
predate this session. Also: fixed lib.rs's "264 B" vs the documented and
now-tested 256 B, added size_of assertions pinning Ccm/CcmEncryptor's sizes
against the memory table (previously undocumented by a test), and added
the CCM aliases to the AES crate's "Modes of operation" section, which
listed every other mode but this one. PR bcgit#126 review, findings F5 and F7.
officialfrancismendoza added a commit to officialfrancismendoza/bc-rust that referenced this pull request Sep 15, 2026
… doesn't have

The Encrypt/Decrypt value help (rendered by clap under --help for every
mode subcommand, including the three CCM ones) said a fresh IV or nonce is
generated and written to the output. CCM's nonce is supplied via --nonce
and never written, so bc-rust aes128-ccm --help printed instructions that
produce "authentication failed" if followed. Trim the shared enum's help
to direction only and point at each subcommand's own --help, which already
documents its mode's exact framing (CBC/CFB/CFB8/CTR already do; CCM's own
help already explains the nonce is supplied, not generated).
PR bcgit#126 review, finding F6.
officialfrancismendoza added a commit to officialfrancismendoza/bc-rust that referenced this pull request Sep 15, 2026
go() called the *_detached one-shots, each of which needs a fresh
ciphertext/plaintext buffer the size of the input on top of the input
buffer already read from stdin. Use Ccm::new plus do_*_update/do_*_final
directly on the buffer already in hand: input.len() is exactly the
declared payload length and is supplied in one call, so the two
do_*_update/do_*_final calls this replaces cannot fail, which the
.expect()s explain. Also: decrypt's tag split now goes through
split_last_chunk_mut, matching Ccm::decrypt's own reasoning for admitting
Clen == Tlen instead of restating the spec's stricter Clen <= Tlen and
then testing < anyway; and the payload-limit error message reads
Ccm::MAX_PAYLOAD_LEN (now pub) instead of re-deriving it. Documented the
packet-AEAD exception to CLAUDE.md's CLI-streams rule this relies on.
PR bcgit#126 review, finding F8 (buffer only; the pre-existing duplicated
nonce-range check is deliberate and stays, per its own comment).
officialfrancismendoza added a commit to officialfrancismendoza/bc-rust that referenced this pull request Sep 15, 2026
… the redundant key check

CcmEncryptor and CcmDecryptor carried seven identical fields and
byte-for-byte identical do_update_aad, differing only in one error string
in do_update_out and in which Ccm direction do_*_final builds; the
"set data_started before the length check" comment was on the encryptor's
copy only. Factor the buffering itself into a private CcmBuffer that both
now wrap as newtypes (the same pattern bouncycastle-ascon uses for
AsconAead128Encryptor/Decryptor), so the shared behavior has one body.

Also: Ccm::checked_perm re-checked KeyType::SymmetricCipherKey, which
P::new (AES_128::new and friends) already checks per
ElectronicCodeBook::new's own documented contract -- confirmed no other
mode in this crate duplicates it, so it bought nothing but a second,
differently-worded error message for the same bad key. Removed, and
Ccm::new/CcmEncryptor/CcmDecryptor now call P::new(key) directly like
every other mode. CcmEncryptor's nonce draw now calls crate::iv::random_iv,
the same OS-backed draw Cbc/Cfb/Ctr already share, instead of a
CCM-specific copy of the same three lines.

No behavior or memory-layout change: CcmEncryptor/CcmDecryptor are still
8400 B at BUFFER_LEN=4096, all 480 ACVP cases and 4 Appendix C vectors
still pass. PR bcgit#126 review, finding F10.
officialfrancismendoza added a commit to officialfrancismendoza/bc-rust that referenced this pull request Sep 15, 2026
…used no private API

crypto/modes/tests/wycheproof_ccm_tests.rs drives bc-test-data's vendored
aes_ccm_test.json (552 tests) through Ccm::encrypt_detached/decrypt_detached,
following the file/skip-with-warning convention acvp_ccm_tests.rs already
uses. Unlike the ACVP set (one nonce length, no malformed inputs), this one
is deliberately adversarial: every nonce length from 8 to 2144 bits, tag
sizes A.1 forbids, truncated and bit-flipped tags. Ccm's NONCE_LEN/TAG_LEN
are const generics restricted to A.1's sets, so a case whose sizes fall
outside them has no instantiation to dispatch to at all -- not a runtime
failure, a compile-time non-option -- and those are counted as skipped
rather than silently dropped. Locally: 486 of 552 cases run (405 valid, 81
invalid), 66 skipped across 63 out-of-range groups, all passing.

bc-test-data/crypto/wycheproof/ already vendors sm4_ccm_test.json for this
exact purpose; aes_ccm_test.json needs adding there too (copied from
https://github.com/C2SP/wycheproof, testvectors_v1) for this suite to run
anywhere but here -- that's a separate repository this PR cannot touch.

Also, per QUALITY_AND_STYLE.md's unit-vs-integration-test rule (a unit
test only where the behaviour cannot be reached from outside): moved
payload_longer_than_the_q_limit_is_refused and
a_short_or_long_payload_is_refused out of ccm.rs's #[cfg(test)] block into
sp800_38c_tests.rs (converted from the toy Identity permutation to
AES_128, matching that file's convention), since both exercise only
Ccm::new/do_encrypt_update/do_encrypt_final. Deleted
both_directions_mac_the_plaintext outright: it was byte-for-byte the same
check as sp800_38c_tests.rs's each_direction_has_its_own_methods, just
against Identity instead of AES_128. What remains in ccm.rs's own test
module is exactly what its module doc says it should be: the private
formatting helpers (format_b0, encode_aad_len, put_q_field) that no public
API exposes directly.

PR bcgit#126 review, finding F9.
@ounsworth
ounsworth marked this pull request as draft September 16, 2026 02:56
…in update_out_len and a FINAL_LEN final buffer so a buffering cipher or an inline ciphertext||tag layout can be expressed; TaggedEncryptor/TaggedDecryptor adapt any FINAL_LEN=0 pair to the SimpleCipherEncryptor/SimpleCipherDecryptor ciphertext||tag shape; the block, simple-cipher and AEAD strength sweeps assert they are not vacuous, and the AEAD streaming suite gains a genuinely-buffering toy plus undersized-buffer and std-one-shot coverage
…XOF128/CXOF128) implementing AEADCipherEncryptor/AEADCipherDecryptor via AsconAead128Encryptor/AsconAead128Decryptor, with HashFactory/XOFFactory registration and CLI wiring including a TaggedDecryptor-based decrypt stream
…OF/XOFSqueezer API and updated factory/CLI/tests/benches to compile against the new API (bcgit#119)
…/officialfrancismendoza/119-core-aead-cipher
…CLI thread, and fix a broken intra-doc link (bcgit#119)

Review follow-ups on the head of bcgit#120; no behaviour changes.

- cli/src/main.rs, cli/src/ascon_cmd.rs: the ascon-aead128 command's help and module docs still
  described the pre-nonce-prefix format ("output = ciphertext||tag") after the command started
  generating a nonce and writing it as the first 16 bytes of the stream. They now spell the
  convention out in both directions, the way aes128-ctr's help does for its own nonce, and say what
  --nonce/--nonce-file turn off -- the part a user gets wrong, since feeding a prefixed ciphertext
  to "--decrypt --nonce ..." decrypts garbage and only then fails the tag check. The two encrypt
  paths each gain a line saying which API they drive and why the explicit-nonce one cannot use the
  AEADCipherEncryptor pair (do_encrypt_init generates the nonce by construction).
- cli/src/main.rs: fn main's 8 MiB thread gains a comment for the constraint it exists for. It is
  load-bearing: with it removed and `ulimit -s 1024`, every subcommand -- sha3-256 as much as
  ascon-aead128 -- overflows during argument parsing in a debug build, before any algorithm runs.
- crypto/ascon/src/lib.rs: [`ascon_aead128::AsconAead128Decryptor::do_decrypt_final`] does not
  resolve, because do_decrypt_final is an AEADCipherDecryptor method rather than an inherent one,
  so `cargo doc` warned and published a dead link. Points at the trait method instead.

Assisted-by: Claude:claude-opus-5

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…#119)

The entry carried the pre-remediation run, flagged as such ("20 missed before the XOF/CXOF
boundary-test additions"). Re-measured on this head with `cargo mutants -p bouncycastle-ascon
--test-package bouncycastle-ascon --jobs 3 --timeout 120`, with bc-test-data reachable from the
copied tree and a config whose examine_globs block is removed: 735 mutants, 618 caught, 111
unviable, 6 missed. The six are the known equivalences already commented at their sites -- the
sponge absorb/squeeze boundaries and the two disjoint-bit `|` -> `^` in set_state_byte -- so the
14 real survivors that run found in the XOF/CXOF Hash view are dead.

Assisted-by: Claude:claude-opus-5

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…eded by the AEADCipherEncryptor/AEADCipherDecryptor split (bcgit#119)

AEADCipher was the single-type AEAD trait this issue exists to split. It had no implementor on the
base branch and its conformance suite had nothing to run against; this PR was about to give it its
first and only implementor, on AsconAead128, in the same change that introduces the pair meant to
replace it. That would have left the library with two parallel AEAD abstractions and Ascon-AEAD128
with four public one-shot encrypt surfaces. Deleted instead:

- crypto/core/src/traits.rs: the trait itself (encrypt/encrypt_out/decrypt/decrypt_out, the
  aead_* pair, do_aead_encrypt_final/do_aead_decrypt_final). The AEADCipherEncryptor doc that
  contrasted its tag placement with this trait's now just points at tagged_aead.
- crypto/core-test-framework/src/symmetric_ciphers.rs: TestFrameworkAEADCipher::test and
  ::test_plain_one_shots, the suites for it. The struct keeps test_encryptor_decryptor and
  test_buffering_toy, which exercise the pair.
- crypto/ascon/src/ascon_aead128.rs: the impl, and the module-doc sentence that justified the
  newtype pair by pointing at it.

Test coverage is kept where it was about Ascon rather than about the trait: the chunk-boundary
sweep and the wrong-tag rejection now drive the inherent do_encrypt_final/do_decrypt_final (they
only used the trait for its finalizers), and the undersized-buffer suite is rewritten against the
inherent one-shots, whose own length checks -- including the 16-byte-ciphertext and oversized-buffer
boundaries that must NOT be rejected -- were previously reached only through the trait. The three
tests that were about the deleted code (the std Vec wrappers, the plain view's
DecryptionFailed remapping, the AEADCipher framework conformance call) go with it.

Assisted-by: Claude:claude-opus-5

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ion figures (bcgit#119)

Deleting the trait and its suites takes bouncycastle-ascon from 735 mutants to 655: 558 caught, 91
unviable, 6 missed, the same six known equivalences as before, so the tests ported onto the
inherent one-shots hold the coverage the deleted trait's tests had.

Assisted-by: Claude:claude-opus-5

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ag layout on the AEAD traits, and address the remaining API-shape review points (bcgit#119)

The `tagged_aead` adapter pair is gone; what it did belongs to the traits themselves.

- crypto/core/src/traits.rs: AEADCipherEncryptor gains `tagged_encrypt` (one-shot into
  `ciphertext || tag`), `tagged_do_aead_encrypt_final` (streaming: flush, then append the tag) and
  `tagged_encrypt_out_len`; AEADCipherDecryptor gains `tagged_decrypt`,
  `tagged_do_aead_decrypt_final` (streaming: the tail is leftover ciphertext followed by the tag)
  and `tagged_decrypt_out_max_len`. All are defaults over the existing methods, so every
  implementor gets both layouts and neither has to be bolted on by a wrapper type that cannot
  express a buffering cipher's lengths (the `FINAL_LEN = 0` restriction TaggedEncryptor and
  TaggedDecryptor carried).
- crypto/core/src/tagged_aead.rs is deleted, with its module declaration and every use of it.
  crypto/core/tests/aead_tagged_tests.rs keeps the toy AEAD the deleted module's in-`src` tests
  used and points it at the new methods: round trip at every length crossing `TAG_LEN`, every
  chunking, tampering, a stream that ends before a whole tag, and every undersized buffer.
- crypto/core-test-framework: the AEAD suite now checks the inline layout for every implementor
  (one-shot against streaming, and a too-short tail as DecryptionFailed), and the buffering toy
  checks it where FINAL_LEN > 0, which is where `tagged_do_aead_encrypt_final` has to flush and
  append in one call. Its short-buffer probe on the decryptor now feeds the decryptor its own
  ciphertext rather than the plaintext, and uses the ciphertext's length.
- crypto/ascon: `AsconAead128::new`'s `for_encryption: bool` is no longer public API --
  `new_encrypting` / `new_decrypting` name the direction, and the bool constructor they share is
  private. The crate docs gain a `tagged_*` example.
- cli/src/ascon_cmd.rs: both directions drive the trait pair, holding the tag back by hand on the
  way in, which is what the adapter did for it. A failed `do_encrypt_init`/`do_decrypt_init` --
  the RNG or the key material -- now prints an error and exits rather than panicking, as
  block_mode_cmd.rs does for the same call, and the remaining unwraps carry their `infallible:`
  notes.
- crypto/core/src/traits.rs also: the allocating one-shot's three-part return is now the named
  `AEADEncrypted<NONCE_LEN, TAG_LEN>` (clippy `type_complexity`), and `decrypt_out` /
  `encrypt_out_rng` get the same "an implementor with FINAL_LEN > 0 must override this" note
  `encrypt_out` already had.

Assisted-by: Claude:claude-opus-5

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…nges (bcgit#119)

661 mutants, 558 caught, 97 unviable, 6 missed -- the same six known equivalences (the sponge
absorb/squeeze boundaries and the two disjoint-bit `|` -> `^` in set_state_byte). The count moves
from 655 with the new_encrypting/new_decrypting constructors.

Assisted-by: Claude:claude-opus-5

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… in the tagged AEAD defaults (bcgit#119)

`cargo mutants -p bouncycastle-core -f crypto/core/src/traits.rs --re
'AEADCipherEncryptor|AEADCipherDecryptor' --test-package bouncycastle-core --test-package
bouncycastle-ascon` reported 116 mutants, 91 caught, 19 unviable, 6 missed. Four of the six were
real: the buffer guards could be weakened without a test noticing, because a too-short buffer is
rejected either by the guard or by the `do_update_out` behind it, and both report
IncorrectOutputBufferLength with the same length -- so the probes could not tell which had fired.

- crypto/core/tests/aead_tagged_tests.rs: `tagged_do_aead_decrypt_final` with a buffer of exactly
  `needed` must succeed. Kills `plaintext.len() < needed` -> `<=` and -> `==`.
- crypto/core-test-framework: the buffering toy now finishes from a tail that still holds
  ciphertext (TAG_LEN + 4 bytes) into an exactly-sized buffer, which is what makes
  `update_out_len(..) + FINAL_LEN` observable -- with a generous buffer any arithmetic there would
  do. Kills `+ FINAL_LEN` -> `* FINAL_LEN`. The AEAD suite also feeds `encrypt_out_rng` a buffer
  with room to spare, so its own guard cannot be flipped to `>` unnoticed.

The re-run is 116 mutants, 95 caught, 19 unviable, 2 missed; the two are `written + final_len` ->
`written - final_len` in `encrypt_out_rng`, equivalent while every implementor has FINAL_LEN = 0.

Assisted-by: Claude:claude-opus-5

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…defaults (bcgit#119)

The ascon crate's numbers were already there; the pair's own defaults in core were only in
f376c14's commit message. 116 mutants, 95 caught, 19 unviable, 2 missed.

Assisted-by: Claude:claude-opus-5

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@dghgit
dghgit force-pushed the feature/officialfrancismendoza/125-AES-lightengine-CCM-mode branch from 660eef8 to 67a7b45 Compare September 24, 2026 01:20
dghgit added a commit to officialfrancismendoza/bc-rust that referenced this pull request Sep 24, 2026
…it#124)

Rebased onto the CCM branch (bcgit#126), GCM no longer compiled against the traits
it was written for. Nothing about its shape changes -- it already implemented the
symmetric pair with FINAL_LEN = TAG_LEN and a decryptor that holds back the
last TAG_LEN bytes -- only names and one signature:

- SimpleCipherEncryptor / SimpleCipherDecryptor are SymmetricCipherEncryptor /
  SymmetricCipherDecryptor, and TestFrameworkSimpleCipher is
  TestFrameworkSymmetricCipher.
- SymmetricCipherError::IncorrectOutputBufferLength(&str, usize) is
  OutputBufferTooSmall(usize).
- StreamCipherDecryptor::do_decrypt now returns the byte count, so Gcm's two
  in-place decrypt paths discard it with `?` and return Ok(()).

Replaying the GCM commit onto CCM conflicted wherever the two add the same
kind of thing: cli/src/main.rs keeps both sets of three subcommands and match
arms (CCM's, then GCM's) and gains GCM's aead_mode_cmd and aes_gcm_cmd
modules; the aes and modes crate docs keep both CCM's and GCM's paragraphs and
table rows. CCM's docs called it the only authenticated mode, in the table, the
overview, the usage section and "Choosing between the modes"; each now says it
is one of two, and the recommendation in that last section is left as written.

cargo mutants -p bouncycastle-modes -f crypto/modes/src/gcm.rs --re
'Gcm.*::(do_decrypt|verify_then_decrypt|do_update_out)' --test-package
bouncycastle-modes (the functions this touches), with the four survivors
re-run against bouncycastle-aes and cli as well: 43 mutants, 29 caught,
11 unviable, 3 missed -- the three `> 0` guards in the decryptor's
do_update_out, unchanged here. Two guard zero-length copies and are
equivalent; the third skips do_decrypt when nothing is released, which may
leave the AAD phase open after a first do_update_out shorter than TAG_LEN.

Assisted-by: Claude:claude-opus-5-5

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…/125-AES-lightengine-CCM-mode

Brings in b8a217f / 9da20d3 (AES_128 / AES_192 / AES_256 renamed AES128Internal / AES192Internal /
AES256Internal, and the aes mode modules made public with the crate-level usage section cut to a
list of links) and 334cd2b (ElectronicCodeBook's batch methods now required).

- aes/src/lib.rs: upstream's link list plus AES_CCM; `ccm` is a `pub mod` like the other modes.
- modes/src/lib.rs, modes/benches/modes_benches.rs: this branch's CCM additions, renamed.
- The CCM code, tests, CLI and mem bench use the new AES*Internal names (bench group IDs left as
  upstream leaves its own).
- The test-only `Identity` permutation in modes/src/ccm.rs gains the four batch methods.

Assisted-by: Claude:claude-opus-5-5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@dghgit dghgit left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review report sent. It lists three findings, each with its location, a code excerpt, how it would fail, and a suggested fix:

  1. Batched keystream left on the stack: up to 64 bytes per batch, never cleared.
  2. An empty do_update_out call locks out AAD.
  3. One-shot encryption ignores FINAL_LEN: something one-shot encrypted can then be rejected by the same type's streaming decryptor.

I think once these are done this one is good to go, but get me to check it once more.

…officialfrancismendoza/125-AES-lightengine-CCM-mode
…25-AES-lightengine-CCM-mode' into feature/officialfrancismendoza/125-AES-lightengine-CCM-mode

Merged origin CCM history into updated xof-cshake CCM branch
dghgit and others added 7 commits September 27, 2026 16:12
…id for CCM

Ctr::apply_batch built its 2- or 4-block keystream in a plain local, and
refill/apply_one enciphered into a plain local before copying into the
Secret, so live keystream was left on the stack unzeroized -- the exact
leak 2161a04 fixed in the CCM copy of this code. The batch scratch is now
one Secret per width held for the whole apply() call rather than one per
batch, and refill/apply_one encipher in place inside the Secret.

Assisted-by: Claude:claude-fable-5-1
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…or a short inline C, empty update keeps the AAD phase open, per-side capacity messages, hoisted keystream scratch

* CcmDecryptor now carries the same NONCE_LEN >= 12 assertion as
  CcmEncryptor (moved into CcmBuffer and run from every entry point of
  both, one-shots included), so a parameter set compiles for both sides
  or neither; compile_fail doctest added for the decryptor.
* An inline ciphertext shorter than TAG_LEN is DecryptionFailed from all
  three inline entry points (Ccm::decrypt previously said GenericError),
  the variant SymmetricCipherDecryptor::do_final specifies for a
  malformed ciphertext.
* CcmBuffer::do_update_out with an empty slice is a no-op and no longer
  closes the AAD phase, matching the trait's empty-AAD-at-any-point rule.
* The over-capacity message names the caller's real bound: the encryptor
  is limited to FINAL_LEN - TAG_LEN, the decryptor to FINAL_LEN.
* The batched keystream Secret is held per apply_keystream call instead
  of per 2/4-block batch, the same shape Ctr now uses.
* Docs: short tags are justified by Appendix C.1/C.2 (Tlen=32, 48), not
  the ACVP set, which has only 96- and 128-bit tags; the AAD sharing
  FINAL_LEN's bound is stated on CcmEncryptor with the sizing rule.

Tests: sp800_38c_tests gains an_empty_update_does_not_close_the_aad_phase,
extends the short-ciphertext test to all three entry points, and pins the
encryptor's message to its bound. SP 800-38C Appendix C parameters
verified against the downloaded PDF.

Assisted-by: Claude:claude-fable-5-1
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ptor too, and the AAD shares FINAL_LEN's bound

Assisted-by: Claude:claude-fable-5-1
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…n the q limit on decrypt as well as encrypt

A --nonce-file written with echo rather than echo -n is a valid 13-byte
nonce, so it was silently accepted as a different nonce from the 12 bytes
intended; the bytes are still used as they are (stripping would collapse
two distinct nonces into one), but stderr now says so and gives the
remedy. The decrypt arm printed the raw Debug form of Ccm::new's payload
limit error; both arms now go through one helper that reports the payload
length, q and the limit.

Assisted-by: Claude:claude-fable-5-1
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… claimed to, and record what it measures

bench_buffering_* called the one-shots, which CcmEncryptor/CcmDecryptor
override to bypass the buffer, so the "3 * BUFFER_LEN" the header claimed
was never exercised, and BUFFER_LEN was the pre-rename name. The benches
now drive do_*_init -> do_update_out -> do_final (and the detached final),
the one-shot is kept as its own bench for the "bypasses the buffer" claim,
FINAL_LEN is 16 KiB so every path clears massif's ~7.7 KB start-up floor,
and the message is pinned through a black_box reference so the compiler
places it identically in every bench. Measured figures are in the header:
the one-shot is within 1.3 KB (the DRBG) of the direct path, and the
streaming path costs about 7 * FINAL_LEN, not 3, because each consuming
final takes the 2 * FINAL_LEN value by value.

Assisted-by: Claude:claude-fable-5-1
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Ccm and CcmEncryptor are imported into crypto/aes/src/ccm.rs, so
[`Ccm`](bouncycastle_modes::Ccm) and its CcmEncryptor twin resolve without
the target, and `cargo doc` with -D warnings failed on them with
rustdoc::redundant_explicit_links.

Assisted-by: Claude:claude-opus-5-5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ne the table supports

bench_streaming_encrypt's doc said ~7 * FINAL_LEN above
bench_direct_encrypt_detached, but the measured 134 968 B is ~6.1x above
that bench (which also holds a ciphertext array) and ~7.2x above the
message array alone, which is how the module header states it. The
decrypt bench holds both the message and the sealed array, and its ~7x
(149 976 B, ~7.15x) is against the two of them, not the sealed array
alone. No figures change.

Assisted-by: Claude:claude-opus-5-5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@dghgit dghgit left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There were a couple of nits which I've just fixed.

I'll let Mike know this one is good to go.

officialfrancismendoza pushed a commit to officialfrancismendoza/bc-rust that referenced this pull request Sep 27, 2026
…/119-core-aead-cipher

Resolves the two import conflicts from 8823c7b, which moved SecurityStrength
out of core::traits into core::security_strength: cli/src/helpers.rs and
core-test-framework/src/symmetric_ciphers.rs take the base's new import path
and this branch's AEADCipherEncryptor/AEADCipherDecryptor names. The ascon
crate, core's aead_tagged_tests and cli/src/ascon_cmd.rs exist only on this
side, so the base's mechanical import move is applied to them here; the
result matches the resolution already carried by PR bcgit#126 file for file.

Assisted-by: Claude:claude-fable-5-1
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…/officialfrancismendoza/125-AES-lightengine-CCM-mode

Brings in f8f7a2e (via 8575ea7), feature/simple-ciphers' fix for Ctr's
keystream being left in unzeroized stack arrays -- the same leak 985eb94
already fixed on this branch. The one conflict, ctr.rs's refill,
apply_batch and apply_one, takes this branch's 985eb94 versions (one
Secret per batch width held for the whole apply() call, rather than one
per batch), keeping f8f7a2e's module-doc sentence; the crate docs' Memory
Usage note is reworded to match. Resolving it here means the same
conflict does not recur when this branch lands on feature/xof-cshake.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@officialfrancismendoza
officialfrancismendoza marked this pull request as ready for review September 27, 2026 20:46
@officialfrancismendoza
officialfrancismendoza marked this pull request as draft September 27, 2026 20:48
@dghgit
dghgit merged commit 3ecabfd into bcgit:feature/xof-cshake Sep 28, 2026
8 checks passed
dghgit added a commit that referenced this pull request Sep 28, 2026
Brings in the SP 800-185 work, the core AEAD traits and ascon wiring, and
PRs #126 (AES CCM) and #132 (AES GCM). Merged cleanly: git's rename
detection carried xof-cshake's padding changes into 013d806's
padded_block_cipher.rs, and no xof-cshake file uses the old
PaddedEncryptor/PaddedDecryptor names in code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dghgit added a commit that referenced this pull request Sep 28, 2026
Brings in feature/xof-cshake (SP 800-185, AEAD traits, ascon, PRs #126 CCM
and #132 GCM) and 013d806's rename of PaddedEncryptor/PaddedDecryptor to
PaddedBlockCipherEncryptor/PaddedBlockCipherDecryptor.

- cli/src/block_mode_cmd.rs: BlockModeAction takes upstream's short variant
  docs, since CCM now shares the enum and the per-mode IV/nonce framing no
  longer holds for every subcommand; DecryptOnlyAction kept.
- mem_usage_benches/Cargo.toml: both the ccm and tdes bench binaries.
- cli/src/aes_ccm_cmd.rs: BLOCK_LEN from bouncycastle::aes, as the other
  aes_*_cmd.rs files here do, since this branch's block_mode_cmd is
  generic over the block length and no longer exports one.
- crypto/tdes: the padding adapters under their new names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dghgit added a commit that referenced this pull request Sep 28, 2026
Brings in feature/xof-cshake (SP 800-185, AEAD traits, ascon, PRs #126 CCM
and #132 GCM) and 013d806's rename of PaddedEncryptor/PaddedDecryptor to
PaddedBlockCipherEncryptor/PaddedBlockCipherDecryptor.

- crypto/padding: PaddedMode stays in this crate (this branch's move),
  importing the adapters from crate under their new names; lib.rs exports
  padded_block_cipher and padded_mode.
- crypto/aes/src/lib.rs: gains pub mod gcm; mod padded_mode stays gone.
- mem_usage_benches/Cargo.toml: both the ccm and sm4 bench binaries.
- alpha_0.1.3_release_notes.md: the SM4 bullet and upstream's ascon and
  AEAD-trait notes.
- crypto/sm4: the padding adapters under their new names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dghgit added a commit that referenced this pull request Sep 28, 2026
Brings in feature/xof-cshake (SP 800-185, AEAD traits, ascon, PRs #126 CCM
and #132 GCM) and 013d806's rename of PaddedEncryptor/PaddedDecryptor to
PaddedBlockCipherEncryptor/PaddedBlockCipherDecryptor. Resolved as on
feature/sm4:

- crypto/padding: PaddedMode stays in this crate, importing the adapters
  from crate under their new names; lib.rs exports padded_block_cipher
  and padded_mode.
- crypto/aes/src/lib.rs: gains pub mod gcm; mod padded_mode stays gone.
- mem_usage_benches/{Cargo.toml,src/lib.rs}: both the camellia and ccm
  benches.
- alpha_0.1.3_release_notes.md: the Camellia bullet and upstream's ascon
  and AEAD-trait notes.
- crypto/camellia: the padding adapters under their new names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dghgit added a commit that referenced this pull request Sep 28, 2026
Brings in feature/xof-cshake (SP 800-185, AEAD traits, ascon, PRs #126 CCM
and #132 GCM) and 013d806's rename of PaddedEncryptor/PaddedDecryptor to
PaddedBlockCipherEncryptor/PaddedBlockCipherDecryptor. Resolved as on
feature/sm4 and feature/camellia:

- crypto/padding: PaddedMode stays in this crate, importing the adapters
  from crate under their new names; lib.rs exports padded_block_cipher
  and padded_mode.
- crypto/aes/src/lib.rs: gains pub mod gcm; mod padded_mode stays gone.
- Cargo.toml, src/lib.rs, cli/src/main.rs: the aria and ascon entries
  side by side.
- mem_usage_benches/{Cargo.toml,src/lib.rs}: both the aria and ccm
  benches.
- alpha_0.1.3_release_notes.md: the ARIA bullet and upstream's ascon and
  AEAD-trait notes.
- crypto/aria: the padding adapters under their new names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dghgit added a commit that referenced this pull request Sep 28, 2026
…/ecdsa

Brings in xof-cshake 6705a3f: PRs #126 (AES CCM) and #132 (AES GCM), the
AEAD traits and ascon, feature/simple-ciphers' Ctr keystream fix and
013d806's padding-adapter rename (not used on this branch). The only
conflicts are mem_usage_benches/{Cargo.toml,src/lib.rs}, resolved to
keep the ccm, ecdsa and sm2 benches.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants